package org.base.sys.service.impl;


import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.base.ConstantPool;
import org.base.common.exception.BusinessException;
import org.base.common.exception.ExceptionEnum;
import org.base.common.service.impl.ServiceImpl;
import org.base.common.util.*;
import org.base.sys.VO.*;
import org.base.sys.dao.SysUserMapper;
import org.base.sys.enums.LoginStatus;
import org.base.sys.enums.UserStatus;
import org.base.sys.pojo.SysLoginLog;
import org.base.sys.pojo.SysMenuPermission;
import org.base.sys.pojo.SysRole;
import org.base.sys.pojo.SysUser;
import org.base.sys.query.SysUserQuery;
import org.base.sys.service.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户信息 服务实现类
 * </p>
 *
 * @author AutoGenerator
 * @since 2020-05-27
 */
@Service
@Slf4j
@AllArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser, SysUserQuery> implements SysUserService {


    private SysUserRoleService userRoleService;

    private SysRoleService roleService;

    private SysLoginLogService loginLogService;

    private SysMenuPermissionService menuPermissionService;

    @Override
    public SysUserVO getUserVO(Long userId) {
        SysUserVO userVO = null;
        if (userId != null) {
            final SysUser sysUser = getById(userId);
            userVO = ObjectCopyUtils.copy(sysUser, SysUserVO.class);
            userVO.setMenus(getUserMenus(userId));
        }
        return userVO;
    }

    private List<Long> getUserRoleIds(Long userId) {
        final List<SysRole> userRoles = getUserRoles(userId);
        if (CollectionUtils.isNotEmpty(userRoles)) {
            return userRoles.stream().map(SysRole::getId).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }

    public boolean isAdmin(Long userId) {
        final List<SysRole> userRoles = getUserRoles(userId);
        if (CollectionUtils.isNotEmpty(userRoles)) {
            for (SysRole userRole : userRoles) {
                if (userRole.getCode() != null && userRole.getCode().equals(ConstantPool.ADMIN_CODE)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    @Transactional
    public boolean saveOrUpdateUser(SysUserVO entity) throws NoSuchAlgorithmException {
        final Long id = entity.getId();
        if (exist(id, entity.getLoginName(), entity.getEmail(), entity.getPhoneNumber(), entity.getUserName())) {
            BusinessException.throwEx(ExceptionEnum.USER_EXIST);
        }
        SysUser user;
        if (id != null) {
            user = getById(id);
            user.setUserName(entity.getUserName());
            user.setEmail(entity.getEmail());
            user.setPhoneNumber(entity.getPhoneNumber());
            user.setSex(entity.getSex());
            user.setAvatar(entity.getAvatar());
            if (entity.getStatus() != null) {
                user.setStatus(entity.getStatus());
            }
        } else {
            final String salt = PasswordHelper.getSalt();
            user = ObjectCopyUtils.copy(entity, SysUser.class);
            user.setSalt(salt);
            user.setPassword(PasswordHelper.encryption(user.getPassword(), salt));
        }
        saveOrUpdate(user);
        userRoleService.updateUserRole(user.getId(), entity.getRoleIds());
        return true;
    }

    @Override
    public SysUserVO getUserVOById(Long id) {
        final SysUser user = getById(id);
        if (user != null) {
            final SysUserVO userVO = ObjectCopyUtils.copy(user, SysUserVO.class);
            List<Long> roleIds = userRoleService.getUserRoleIds(id);
            userVO.setRoleIds(roleIds);
            return userVO;
        }
        return null;
    }

    @Override
    public SysUserVO getUserVOByLoginName(String loginName) {
        final SysUser user = getByUserName(loginName);
        if (user != null) {
            List<SysRole> userRoles = getUserRoles(user.getId());
            final SysUserVO userVO = ObjectCopyUtils.copy(user, SysUserVO.class);
            userVO.setRoles(userRoles);
            return userVO;
        }
        return null;
    }

    @Override
    public List<String> getRolesPermissions(List<SysRole> roles) {
        if (CollectionUtils.isNotEmpty(roles)) {
            final List<Long> roleIds = roles.stream().map(SysRole::getId).collect(Collectors.toList());
            return roleService.getRolePermissions(roleIds);
        }
        return null;
    }

    @Override
    public SysUserVO login(String username, String password) throws NoSuchAlgorithmException {
        SysUser sysUser = getByUserName(username);
        final HttpServletRequest request = RequestUtils.getRequest();
        final UserAgent userAgent = new UserAgent(request.getHeader("User-Agent"));
        SysLoginLog loginLog = new SysLoginLog();
        loginLog.setLoginName(username);
        loginLog.setIpAddr(IpUtils.getIpAddr(request));
        loginLog.setBrowser(userAgent.getBrowser().getName());
        loginLog.setOs(userAgent.getOperatingSystem().getName());

        if (sysUser == null) {
            loginLog.setStatus(LoginStatus.FAILURE.getCode());
            AsyncUtils.execute(() -> {
                loginLog.setLoginLocation(AddressUtils.getRealAddressByIP(loginLog.getIpAddr()));
                loginLog.setMsg(ExceptionEnum.USERNAME_OR_PASSWORD_ERROR.getMsg());
                loginLogService.save(loginLog);
            });
            BusinessException.throwEx(ExceptionEnum.USERNAME_OR_PASSWORD_ERROR);
        }
        if (sysUser.getStatus() == UserStatus.DISABLE.getCode()) {
            loginLog.setStatus(LoginStatus.FAILURE.getCode());
            AsyncUtils.execute(() -> {
                loginLog.setLoginLocation(AddressUtils.getRealAddressByIP(loginLog.getIpAddr()));
                loginLog.setMsg(ExceptionEnum.USER_DISABLE.getMsg());
                loginLogService.save(loginLog);
            });
            BusinessException.throwEx(ExceptionEnum.USER_DISABLE);
        }
        final SysUserVO userVO = ObjectCopyUtils.copy(sysUser, SysUserVO.class);
        if (!PasswordHelper.checkPassword(password, userVO.getSalt(), userVO.getPassword())) {
            loginLog.setStatus(LoginStatus.FAILURE.getCode());
            AsyncUtils.execute(() -> {
                loginLog.setLoginLocation(AddressUtils.getRealAddressByIP(loginLog.getIpAddr()));
                loginLog.setMsg(ExceptionEnum.USERNAME_OR_PASSWORD_ERROR.getMsg());
                loginLogService.save(loginLog);
            });
            BusinessException.throwEx(ExceptionEnum.USERNAME_OR_PASSWORD_ERROR);
        }
        loginLog.setStatus(LoginStatus.SUCCESS.getCode());
        AsyncUtils.execute(() -> {
            loginLog.setLoginLocation(AddressUtils.getRealAddressByIP(loginLog.getIpAddr()));
            loginLog.setMsg("登录成功");
            loginLogService.save(loginLog);
        });
        return userVO;
    }

    private SysUser getByUserName(String username) {
        return lambdaQuery().eq(SysUser::getLoginName, username)
                .or().eq(SysUser::getEmail, username)
                .or().eq(SysUser::getPhoneNumber, username)
                .one();
    }

    private boolean exist(Long id, String loginName, String email, String phoneNumber, String userName) {
        final LambdaQueryChainWrapper<SysUser> lambdaQuery = lambdaQuery();
        lambdaQuery.ne(id != null, SysUser::getId, id);
        lambdaQuery.and(lq -> lq.eq(StringUtils.isNotBlank(loginName), SysUser::getLoginName, loginName)
                .or().eq(StringUtils.isNotBlank(email), SysUser::getEmail, email)
                .or().eq(StringUtils.isNotBlank(phoneNumber), SysUser::getPhoneNumber, phoneNumber)
                .or().eq(StringUtils.isNotBlank(userName), SysUser::getUserName, userName));
        return lambdaQuery.count() > 0;
    }

    @Override
    public List<SysRole> getUserRoles(Long userId) {
        List<Long> roleIds = userRoleService.getUserRoleIds(userId);
        if (CollectionUtils.isNotEmpty(roleIds)) {
            return roleService.getRoleByIds(roleIds);
        }
        return null;
    }

    @Override
    public List<MenuVO> getUserMenus(Long userId) {
        final List<SysRole> userRoles = getUserRoles(userId);
        return roleService.getRoleMenus(userRoles);
    }

    @Override
    public boolean removeById(Serializable id) {
        if (UserUtils.getUserId().equals(id)) {
            BusinessException.throwEx(ExceptionEnum.ERROR_DELETE_SELF);
        }
        return super.removeById(id);
    }

    @Override
    public boolean hasPerm(String perm) {
        final Long userId = UserUtils.getUserId();
        if (isAdmin(userId)) {
            return true;
        }
        if (StringUtils.isBlank(perm)) {
            BusinessException.throwEx(ExceptionEnum.FORBID_ACCESS);
        }
        final List<Long> userRoleIds = getUserRoleIds(UserUtils.getUserId());
        final List<SysMenuPermission> rolePerms = menuPermissionService.getRolePerms(userRoleIds);
        if (CollectionUtils.isNotEmpty(rolePerms)) {
            final Set<String> permSet = rolePerms.stream().map(SysMenuPermission::getPermKey).collect(Collectors.toSet());
            return permSet.contains(perm);
        }
        return false;
    }

    @Override
    public SysUserProfileVO getUserProfile() {
        final Long userId = UserUtils.getUserId();
        final SysUser sysUser = getById(userId);
        final SysUserProfileVO profileVO = ObjectCopyUtils.copy(sysUser, SysUserProfileVO.class);
        return profileVO;
    }

    @Override
    public boolean updateUserProfile(SysUserVO entity) {
        SysUser user = getById(entity.getId());
        user.setUserName(entity.getUserName());
        user.setEmail(entity.getEmail());
        user.setPhoneNumber(entity.getPhoneNumber());
        user.setSex(entity.getSex());
        user.setAvatar(entity.getAvatar());
        return saveOrUpdate(user);
    }

    @Override
    @Transactional
    public boolean changePassword(ChangePasswordVO changePasswordVO) throws NoSuchAlgorithmException {
        final Long userId = UserUtils.getUserId();
        final SysUser sysUser = getById(userId);
        if (!PasswordHelper.checkPassword(changePasswordVO.getPassword(), sysUser.getSalt(), sysUser.getPassword())) {
            BusinessException.throwEx(ExceptionEnum.PASSWORD_ERROR);
        }
        final String salt = PasswordHelper.getSalt();
        sysUser.setPassword(PasswordHelper.encryption(changePasswordVO.getNewPassword(), salt));
        sysUser.setSalt(salt);
        updateById(sysUser);
        return true;
    }

    @Override
    public boolean resetPassword(ResetPasswordVO resetPasswordVO) throws NoSuchAlgorithmException {
        final Long id = resetPasswordVO.getId();
        final SysUser sysUser = getById(id);
        final String salt = PasswordHelper.getSalt();
        final String password = PasswordHelper.encryption(resetPasswordVO.getPassword(), salt);
        sysUser.setSalt(salt);
        sysUser.setPassword(password);
        updateById(sysUser);
        return true;
    }
}
